import type TerrainSchematic from "../schematic";
import { normalizeAndUpdateBounds, optimizePalette } from "./";

export interface ErosionOptions {
  /** 模拟的水滴迭代次数 */
  iterations: number;
  /** 侵蚀速度 (0-1) */
  erosionSpeed: number;
  /** 沉积速度 (0-1) */
  depositSpeed: number;
  /** 惯性 (0-1) */
  inertia: number;
  /** 重力 */
  gravity: number;
  /** 最小坡度，低于此值的地块将被视为平地 */
  minSlope: number;
  /** 水滴可以携带的最大泥沙量 */
  sedimentCapacityFactor: number;
  /** 水滴蒸发速度 (0-1) */
  evaporationSpeed: number;
  /** 水滴初始水量 */
  startWater: number;
  /** 水滴初始速度 */
  startSpeed: number;
}

/**
 * 使用基于水滴的液压侵蚀算法来模拟地形风化。
 * @param schematic 要处理的蓝图。
 * @param userOptions 用户自定义的侵蚀参数。
 * @returns `this`，允许链式调用。
 */
export function erode(
  schematic: TerrainSchematic,
  userOptions?: Partial<ErosionOptions>
): TerrainSchematic {
  const options: ErosionOptions = {
    iterations: 50000,
    erosionSpeed: 0.3,
    depositSpeed: 0.3,
    inertia: 0.1,
    gravity: 4,
    minSlope: 0.01,
    sedimentCapacityFactor: 4,
    evaporationSpeed: 0.01,
    startWater: 1,
    startSpeed: 1,
    ...userOptions,
  };

  // 1. 初始化高度图和方块类型图
  const { size, heightMap, materialMap } =
    initializeHeightAndMaterialMaps(schematic);
  if (size.x === 0 || size.z === 0) {
    return schematic; // 如果没有可处理的维度，则直接返回
  }

  for (let i = 0; i < options.iterations; i++) {
    // 随机选择一个点作为水滴的起始位置
    let posX = Math.random() * (size.x - 1);
    let posY = Math.random() * (size.z - 1);

    // 初始化水滴属性
    let dirX = 0;
    let dirY = 0;
    let speed = options.startSpeed;
    let water = options.startWater;
    let sediment = 0;

    for (let lifetime = 0; lifetime < 30; lifetime++) {
      const nodeX = Math.floor(posX);
      const nodeY = Math.floor(posY);
      const dropletIndex = nodeX + nodeY * size.x;

      // 计算偏移量，用于双线性插值
      const cellOffsetX = posX - nodeX;
      const cellOffsetY = posY - nodeY;

      // 计算水滴所在位置的高度和坡度
      const {
        height: currentHeight,
        gradientX,
        gradientY,
      } = calculateHeightAndGradient(heightMap, size, posX, posY);

      // 更新方向
      dirX = dirX * options.inertia - gradientX * (1 - options.inertia);
      dirY = dirY * options.inertia - gradientY * (1 - options.inertia);

      // 标准化方向向量
      const len = Math.sqrt(dirX * dirX + dirY * dirY);
      if (len > 1e-6) {
        dirX /= len;
        dirY /= len;
      }

      // 更新水滴位置
      posX += dirX;
      posY += dirY;

      // 检查是否出界
      if (posX < 0 || posX >= size.x - 1 || posY < 0 || posY >= size.z - 1) {
        break;
      }

      const newHeight = calculateHeightAndGradient(
        heightMap,
        size,
        posX,
        posY
      ).height;
      const deltaHeight = newHeight - currentHeight;

      // 计算泥沙容量
      const sedimentCapacity = Math.max(
        -deltaHeight * speed * water * options.sedimentCapacityFactor,
        options.minSlope
      );

      // 处理侵蚀或沉积
      if (sediment > sedimentCapacity || deltaHeight > 0) {
        const amountToDeposit =
          deltaHeight > 0
            ? Math.min(sediment, deltaHeight)
            : (sediment - sedimentCapacity) * options.depositSpeed;
        sediment -= amountToDeposit;

        // 分配沉积物
        distributeSediment(heightMap, size, posX, posY, amountToDeposit);
      } else {
        const amountToErode = Math.min(
          (sedimentCapacity - sediment) * options.erosionSpeed,
          -deltaHeight
        );

        // 从四个相邻节点移除泥沙
        erodeSediment(
          heightMap,
          size,
          posX,
          posY,
          amountToErode,
          cellOffsetX,
          cellOffsetY
        );
        sediment += amountToErode;
      }

      // 更新速度和水量
      speed = Math.sqrt(
        Math.max(0, speed * speed + deltaHeight * options.gravity)
      );
      water *= 1 - options.evaporationSpeed;
    }
  }

  // 最后, 将高度图转换回方块
  updateSchematicFromHeightMap(schematic, heightMap, materialMap);

  // 确保在操作后更新边界和调色板
  normalizeAndUpdateBounds(schematic);
  optimizePalette(schematic);

  return schematic;
}

/**
 * 从蓝图的方块数据初始化高度图和材质图。
 */
function initializeHeightAndMaterialMaps(schematic: TerrainSchematic) {
  const size = schematic.getDimensions();
  const heightMap = new Float32Array(size.x * size.z).fill(-1);
  // materialMap 存储每个 (x,z) 坐标上最高方块的调色板索引
  const materialMap = new Uint32Array(size.x * size.z);

  for (const block of schematic.schematicBlocks) {
    const index = block.x + block.z * size.x;
    if (block.y > heightMap[index]) {
      heightMap[index] = block.y;
      materialMap[index] = block.p;
    }
  }

  return { size, heightMap, materialMap };
}

/**
 * 计算给定坐标点的高度和梯度（坡度）。
 */
function calculateHeightAndGradient(
  heightMap: Float32Array,
  size: { x: number; z: number },
  posX: number,
  posY: number
) {
  const nodeX = Math.floor(posX);
  const nodeY = Math.floor(posY);

  // 双线性插值计算精确高度
  const x0 = nodeX;
  const x1 = nodeX + 1;
  const y0 = nodeY;
  const y1 = nodeY + 1;

  const h00 = heightMap[x0 + y0 * size.x];
  const h10 = heightMap[x1 + y0 * size.x];
  const h01 = heightMap[x0 + y1 * size.x];
  const h11 = heightMap[x1 + y1 * size.x];

  const tx = posX - x0;
  const ty = posY - y0;

  const height =
    h00 * (1 - tx) * (1 - ty) +
    h10 * tx * (1 - ty) +
    h01 * (1 - tx) * ty +
    h11 * tx * ty;

  // 计算梯度
  const gradientX = (h10 - h00) * (1 - ty) + (h11 - h01) * ty;
  const gradientY = (h01 - h00) * (1 - tx) + (h11 - h10) * tx;

  return { height, gradientX, gradientY };
}

/**
 * 将沉积物分配到周围的节点。
 */
function distributeSediment(
  heightMap: Float32Array,
  size: { x: number; z: number },
  posX: number,
  posY: number,
  amount: number
) {
  const nodeX = Math.floor(posX);
  const nodeY = Math.floor(posY);
  const cellOffsetX = posX - nodeX;
  const cellOffsetY = posY - nodeY;

  // 分配到4个最近的节点
  heightMap[nodeX + nodeY * size.x] +=
    amount * (1 - cellOffsetX) * (1 - cellOffsetY);
  heightMap[nodeX + 1 + nodeY * size.x] +=
    amount * cellOffsetX * (1 - cellOffsetY);
  heightMap[nodeX + (nodeY + 1) * size.x] +=
    amount * (1 - cellOffsetX) * cellOffsetY;
  heightMap[nodeX + 1 + (nodeY + 1) * size.x] +=
    amount * cellOffsetX * cellOffsetY;
}

/**
 * 从周围节点侵蚀泥沙。
 */
function erodeSediment(
  heightMap: Float32Array,
  size: { x: number; z: number },
  posX: number,
  posY: number,
  amount: number,
  cellOffsetX: number,
  cellOffsetY: number
) {
  const nodeX = Math.floor(posX);
  const nodeY = Math.floor(posY);

  // 从4个最近的节点按权重移除泥沙
  const w00 = (1 - cellOffsetX) * (1 - cellOffsetY);
  const w10 = cellOffsetX * (1 - cellOffsetY);
  const w01 = (1 - cellOffsetX) * cellOffsetY;
  const w11 = cellOffsetX * cellOffsetY;

  const h00 = nodeX + nodeY * size.x;
  const h10 = nodeX + 1 + nodeY * size.x;
  const h01 = nodeX + (nodeY + 1) * size.x;
  const h11 = nodeX + 1 + (nodeY + 1) * size.x;

  heightMap[h00] -= amount * w00;
  heightMap[h10] -= amount * w10;
  heightMap[h01] -= amount * w01;
  heightMap[h11] -= amount * w11;
}

/**
 * 根据最终的高度图和材质图更新蓝图的方块列表。
 */
function updateSchematicFromHeightMap(
  schematic: TerrainSchematic,
  heightMap: Float32Array,
  materialMap: Uint32Array
) {
  schematic.schematicBlocks = [];
  const size = schematic.getDimensions(); // 重新获取以防万一

  for (let x = 0; x < size.x; x++) {
    for (let z = 0; z < size.z; z++) {
      const index = x + z * size.x;
      const height = Math.round(heightMap[index]);
      if (height > -1) {
        // 从最高点向下填充直到原来的最低点或遇到阻挡
        // 这一步简化了处理，只保留了表面
        schematic.schematicBlocks.push({
          x,
          y: height,
          z,
          p: materialMap[index],
        });
      }
    }
  }
}
